// 一个基于 stomp.js 封装的 webSoket.js
import Stomp from 'stompjs'

const protocol = window.location.protocol === 'https:'?'wss':'ws';

const webSoketPath = localStorage.getItem('webSoketPath')
// 最大的链接次数
const max_retry_count = 5;
// 重连时间间隔
const reconnect_interval = 10 * 1000

const debug = true

function log(...msg){
  if(!production && debug){
    console.log("WS", ...msg)
  }
}

function error(...msg){
  if(!production && debug){
    console.log("WS", ...msg)
  }
}

export default {
  client:null,
  remote:'',
  // 是否处于连接中，防止多次调用连接，建立多个 ws连接
  connecting:false,
  // 是否已近连接，可以在断开时区分
  connected:false,
  // 重试次数 
  retryCount:0,
  // 心跳间隔，单位秒，如果小于等于0 则不进行心跳 
  heartbeatInterval:60,
  // 心跳任务
  heartbeat:-1,
  // 订阅列表 用于在重连时重新订阅
  subscribeList:[],
  // 连接前发送的消息栈，将在连接后一并发送
  sendStack:[],
  header:null,
  connectCallback:null,

  connect(header,callback = null,remote = webSoketPath,errorCallback = null){
    // 重连前 要置空 client 
    if(this.client !== null){
      if(typeof callback === 'function'){
        callback();
      }
      return;
    }

    // 创建websocket 服务
    const client = Stomp.over(new WebSocket(remote));
    log('连接websocket服务器。。。',remote,protocol,client);
    
    // 生产环境 或非开发环境关闭日志输出
    if(production || !debug){
      client.debug = false;
    }
    // 设置连接状态
    this.connecting = true;
    this.retryCount += 1;

    // 缓存连接服务器地址，重连时 使用该地址重连
    this.remote = remote;

    // 一旦Stomp 客户端建立了，必须调用它的connect()方法去连接Stomp服务端进行验证。这个方法需要两个参数，用户的登录和密码凭证。
    // 这种情况下，客户端会使用Websocket打开连接，并发送一个CONNECT frame。
    // 这个连接是异步进行的：你不能保证当这个方法返回时是有效连接的。为了知道连接的结果，你需要一个回调函数
    // 但是如果连接失败会发生什么呢？
    // connect()方法接受一个可选的参数(error_callback)，当客户端不能连接上服务端时，这个回调函数error_callback会被调用，该函数的参数为对应的错误对象。

    // 开始连接 
    client.connect(header,(frame)=>{
      log('连接websocket服务成功');
      // 连接成功
      this.retryCount = 0;
      this.connected = true;
      this.connecting = false;
      //  把客户端 存起来 其他状态里 也可以用
      this.client = client;
      this.header = header;

      // 连接成功后 将订阅列表重新进行订阅
      this.resubscribe();
      // 连接成功后将待发送消息发送
      this.resend();

      // 执行回调 
      this.connectCallback(frame);
      // 定时发送心跳 每隔一分钟 发送一次心跳连接 防止连接断开
      if(this.heartbeatInterval>0){
        this.heartbeat = window.setInterval(()=>{
          log('发送心跳连接...');

          //当客户端与服务端连接成功后，可以调用send()来发送STOMP消息。这个方法必须有一个参数，用来描述对应的STOMP的目的地。
          // 另外可以有两个可选的参数：headers，object类型包含额外的信息头部；body，一个String类型的参数。
          // client.send("/queue/test", {priority:9 }, "Hello, STOMP");
          // client会发送一个STOMP发送帧给/queue/test，这个帧包含一个设置了priority为9的header和内容为“Hello, STOMP”的body。

          this.client.send('',{},JSON.stringify({type:'ping'}))
        },this.heartbeatInterval*1000);
      }
    },(err)=>{
      error(`websocket连接异常：${err}`);
      this.client = null;
      clearInterval(this.heartbeat);
      this.heartbeat = -1;
      // 判断时连接失败还是连接（已连接成功后）断开 
      const connectError = {
        type:'0', // 0:连接失败 1：连接断开 2：重连失败 
      };

      if(this.connected){
        connectError.type = '1';
        this.connected =false;
        // 重连
        this.reconnect(null,()=>{
          connectError.type = '2';
          if(typeof errorCallback ==='function'){
            errorCallback(connectError)
          }
        });
      }

      // 连接状态断开
      this.connecting = false;
      if(typeof errorCallback ==='function'){
        errorCallback(errorCallback);
      }
    });
  },


  // 重连服务器
  reconnect(callback=null,errorCallback=null){
    log(`正在尝试连接... 第${this.retryCount}次尝试`);
    const originCallback = this.connectCallback;
    this.connect(this.header,(frame)=>{
      this.connectCallback = originCallback;
      this.connectCallback(frame);
      if(typeof callback ==='function'){
        callback();
      }
    },this.remote,()=>{
      // 连接 失败后进行重连，如果连接次数超过了上限 max_retry_count 上限
      if(this.retryCount>max_retry_count){
        error('重连websocket服务失败：尝试次数超上限');
        errorCallback();
      }else{
        setTimeout(() => {
          this.reconnect(callback,errorCallback);
          // reconnect_interval 重连时间间隔
        }, reconnect_interval);
      }
    });
  },

  // 重新订阅话题列表
  resubscribe(){
    const list = this.subscribeList;
    this.subscribeList = [];
    list.forEach((subscription)=>{
      this.subscribe(subscription.topic,subscription.callback)
    })
  },

  // 重新发送压栈的消息
  resend(){
    log(`开始发送栈消息`);
    const list = this.sendStack;
    this.sendStack = [];
    list.forEach((sendMsg)=>{
      this.send(sendMsg.target,sendMsg.message);
    })
  },

  // 发送消息
  send(){
    log(`发送消息 => target:${target},message:${message}`);
    if(this.client ===null){
      this.sendStack.push({
        target,
        message,
      });
    }else{
      this.client.send(target,this.header,JSON.stringify({
        data:message,
      }));
    }
  },

  // 订阅话题
  subscribe(topic,callback){
    // 如果连接没创建 或者 没连上 就把订阅的地址全部存放在 subscribeList 里
    if(this.client ===null || this.connecting){
      this.subscribeList.push({
        topic,
        callback,
        subscription:null,
      })
    }else{
      log(`开始订阅话题${topic}`);
      const subscription = this.client.subscribe(topic,(response)=>{
        log(`收到订阅话题 ${topic} 的消息 => ${response}`);
        // 这里是把订阅 回传的数据保存起来
        callback(JSON.parse(response.body));
      });
      // 订阅完了 把订阅路径存到 subscribeList 里
      this.subscribeList.push({
        topic,
        callback,
        subscription,
      })
    }
  },

  // 取消订阅的某个话题
  unsubscribe(topic){
    this.subscribeList.some((subscription,i)=>{
      if(subscription.topic === topic){
        log(`取消订阅话题${topic}成功`);
        subscription.subscription.unsubscribe();
        this.subscribeList.splice(i,1); 
      }
      return false

    });
  }, 

  // 取消订阅所有话题
  unsubscribeAll(){
    this.subscribeList.forEach((subscription)=>{
      log(`取消订阅话题 ${subscription.topic}`)
      subscription.subscription.unsubscribe();
    });
    this.subscribeList = [];
  },

  // 关闭 websocket 的链接
  reset(){
    this.unsubscribeAll();
    if(this.heartbeat >=0){
      clearInterval(this.heartbeat);
      this.heartbeat = -1;
    }
    if(this.client){
      this.client.disconnect();
      this.client = null;
    }

    this.remote = '';
    this.sendStack = [];
    this.header = null;
    this.connecting = false;
    this.connected = false;
    this.retryCount = 0;
  },

}